// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.common.lib.soap;

import com.google.api.ads.common.lib.conf.ConfigurationHelper;
import com.google.api.ads.common.lib.conf.ConfigurationProvider;
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;

import org.apache.commons.configuration.Configuration;
import org.reflections.Reflections;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Set;

/**
 * Module for the product/framework.
 *
 * @author Adam Rogal
 */
public class ProductFrameworkModule extends AbstractModule {

  /**
   * The {@code reflections} object is used to scan the classpath for SOAP
   * framework modules. This is an extremely heavy-weight object and should be
   * lazily instantiated.
   */
  @Deprecated
  private static Reflections reflections;

  public ProductFrameworkModule() {}

  @Override
  protected void configure() {}

  protected void configureConfigurations(URL propertiesUrl) {
    bind(Configuration.class).annotatedWith(Names.named("productFramework")).toProvider(
        new ConfigurationProvider(ConfigurationHelper.newList(false, propertiesUrl), null));
  }


  /**
   * Configures which SOAP framework to use. The library will attempt to
   * determine which framework is to be used with this product.
   *
   * In order for this method to succeed without throwing an
   * {@code IllegalStateException}, exactly one module on the classpath must be
   * annotated with {@code moduleAnnotationClass} and an instance of this module
   * must instantiate successfully.
   *
   * @param moduleAnnotationClass the {@code Annotation} used to mark classes
   *        that are valid modules to use with this product. The classpath will
   *        be searched for classes with this annotation.
   * @throws IllegalStateException if the SOAP framework could not be configued.
   *         Possible causes include: no SOAP module is available, more than
   *         one SOAP module is available, or the only SOAP module available
   *         could not be instantiated.
   * @deprecated the SOAP-framework-agnostic *Services utilities, the only
   *             classes which use this method, are now all deprecated.
   */
  @Deprecated
  public static ProductFrameworkModule determineSoapModule(
      Class<? extends Annotation> moduleAnnotationClass) {
    if (reflections == null) {
      reflections = new Reflections(new ConfigurationBuilder()
          .filterInputsBy(new FilterBuilder().include("com\\.google\\.api\\.ads\\..*Module.*"))
          .setUrls(ClasspathHelper.forClassLoader()));
    }
    Set<Class<?>> modulesOnClasspath = reflections.getTypesAnnotatedWith(moduleAnnotationClass);
    if (modulesOnClasspath.isEmpty()) {
      throw new IllegalStateException("Unable to locate a SOAP module by reflection. " +
          "Did you forget to add a plugin specifying which SOAP framework to use with the " +
          "library?");
    }
    if (modulesOnClasspath.size() > 1) {
      throw new IllegalStateException("Located more than one SOAP module on the " +
          "classpath. Did you add more than one plugin specifying which SOAP framework to " +
          "use with the library?");
    }
    try {
      return ((ProductFrameworkModule) modulesOnClasspath.iterator().next().newInstance());
    } catch (InstantiationException e) {
      throw new IllegalStateException("Unable to instantiate SOAP module by reflection. " +
          "Did you add the correct plugin specifying which SOAP framework to use with the " +
          "library?", e);
    } catch (IllegalAccessException e) {
      throw new IllegalStateException("Unable to instantiate SOAP module by reflection. " +
          "Did you add the correct plugin specifying which SOAP framework to use with the " +
          "library?", e);
    }
  }
}
